﻿using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Speech.Synthesis;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using Saber.Plugin;
using Saber.Infrastructure.Storage;
using Saber.Infrastructure;

namespace Saber.Plugin.Dictionary
{
    public class Main : IPlugin, ISettingProvider, ISavable
    {
        private ECDict ecdict;
        private WordCorrection wordCorrection;

        private PluginInitContext context;

        private readonly Settings settings;
        private readonly PluginJsonStorage<Settings> _storage;

        private SpeechSynthesizer synth;

        // These two are only for jumping in MakeResultItem
        private string ActionWord;
        private string QueryWord;

        private static PluginMetadata metadata;

        static Main()
        {
            metadata = new PluginMetadata();
            metadata.ID = "c3406b5c22f04984b0183dae897cab3f";
            metadata.Name = "英汉词典";
            metadata.Description = "本地英汉词典, 支持单词订正.";
            metadata.ActionKeyword = "d";
            metadata.IcoPath = "Images\\dictionary.png";


        }

        public void Save()
        {
            _storage.Save();
        }

        public Control CreateSettingPanel()
        {
            return new DictionarySettings(settings);
        }

        public Main()
        {
            _storage = new PluginJsonStorage<Settings>();
            settings = _storage.Load();
        }

        public void Init(PluginInitContext context)
        {
            string CurrentPath = context.CurrentPluginMetadata.PluginDirectory;
            if(File.Exists(CurrentPath + "/dicts/ecdict.db"))
            {
                ecdict = new ECDict(CurrentPath + "/dicts/ecdict.db");
            }
            else
            {
                var path = Path.Combine(Constant.DataDirectory, Constant.Plugins, typeof(Main).Assembly.GetName().Name) + @"\";
                Helper.ValidateDirectory(path);
                ecdict = new ECDict(path + "/ecdict.db");
            }

            wordCorrection = new WordCorrection(CurrentPath + "/dicts/frequency_dictionary_en_82_765.txt", settings.MaxEditDistance);

            this.context = context;
        }

        Result MakeResultItem(string title, string subtitle, string extraAction = null, string word = null,string icopath= "Images\\dictionary.png")
        {
            string getWord() { return (word ?? QueryWord).Replace("!", ""); }
            // Return true if the user tries to copy (regradless of the result)
            bool CopyIfNeeded(ActionContext e)
            {
                if (!e.SpecialKeyState.AltPressed) return false;
                try
                {
                    Clipboard.SetText(getWord());
                }
                catch (ExternalException ee)
                {
                    context.API.ShowMsg("Copy failed, please try later", ee.Message);
                }
                return true;
            }
            bool ReadWordIfNeeded(ActionContext e)
            {
                if (!e.SpecialKeyState.CtrlPressed) return false;
                if (synth == null)
                {
                    synth = new SpeechSynthesizer();
                    synth.SetOutputToDefaultAudioDevice();
                }
                synth.SpeakAsync(getWord());
                return true;
            }

            Func<ActionContext, bool> ActionFunc;
            if (extraAction != null)
            {
                ActionFunc = e =>
                {
                    if (CopyIfNeeded(e)) return true;
                    if (ReadWordIfNeeded(e)) return false;
                    context.API.ChangeQuery(ActionWord + " " + (word ?? QueryWord) + extraAction);
                    return false;
                };
            }
            else
            {
                ActionFunc = e => {
                    if(CopyIfNeeded(e)) return true;
                    if(ReadWordIfNeeded(e)) return false;
                    if(settings.WordWebsite!="") System.Diagnostics.Process.Start(string.Format(settings.WordWebsite, getWord()));
                    return true;
                };
            }
            return new Result()
            {
                Title = title,
                SubTitle = subtitle,
                IcoPath = icopath,
                Action = ActionFunc
            };
        }

        private Result MakeWordResult(Word word) =>
            MakeResultItem(word.word, (word.phonetic != "" ? ("/" + word.phonetic + "/ ") : "") +
                (settings.ShowEnglishDefinition ? word.definition.Replace("\n", "; ") : word.translation.Replace("\n", "; ")),
                "!", word.word);

        // First-level query.
        // English -> Chinese, supports fuzzy search.
        private List<Result> FirstLevelQuery(Query query)
        {
            bool IsExistsInResults(List<Result> res, string word)
            {
                foreach (var item in res)
                {
                    if (item.Title == word) return true;
                }
                return false;
            }

            string queryWord = query.Search;
            List<Result> results = new List<Result>();

            // Pull fully match first.
            Word fullMatch = ecdict.Query(query.Search);
            if (fullMatch != null) results.Add(MakeWordResult(fullMatch));

            // Then fuzzy search results. (since it's usually only a few)
            List<SymSpell.SuggestItem> suggestions = wordCorrection.Correct(queryWord);
            foreach (var suggestion in suggestions)
            {
                Word word = ecdict.Query(suggestion.term);
                
                if(!IsExistsInResults(results, word.word)) // to avoid repetitive results
                    results.Add(MakeWordResult(word));
            }

            // Lastly, the words beginning with the query.
            var result_begin = ecdict.QueryBeginningWith(queryWord);
            foreach (var word in result_begin)
            {
                if (!IsExistsInResults(results, word.word))
                    results.Add(MakeWordResult(word));
            }

            return results;
        }

        // Detailed information of a word.
        // English -> Phonetic, Translation, Definition, Exchanges, Synonym
        // Fuzzy search disabled.
        private List<Result> DetailedQuery(Query query)
        {
            string queryWord = query.Search.Substring(0, query.Search.Length - 1); // Remove the !

            List<Result> results = new List<Result>();

            var word = ecdict.Query(queryWord);

            if (word.phonetic != "")
                results.Add(MakeResultItem(word.phonetic, "音标"));
            if (word.translation != "")
                results.Add(MakeResultItem("释义", word.translation.Replace("\n", "; "), "t"));
            if (word.definition != "")
                results.Add(MakeResultItem("定义", word.definition.Replace("\n", "; "), "d"));
            if (word.exchange != "")
                results.Add(MakeResultItem("词态变化", word.exchange, "e"));

            return results;
        }

        // Translations of a word.
        // English -> Translations
        // Fuzzy search disabled.
        private List<Result> TranslationQuery(Query query)
        {
            string queryWord = query.Search.Substring(0, query.Search.Length - 2); // Get the word

            List<Result> results = new List<Result>();

            var word = ecdict.Query(queryWord);

            foreach (var translation in word.translation.Split('\n'))
            {
                results.Add(MakeResultItem(translation, "释义"));
            }

            return results;
        }

        // Definitions of a word.
        // English -> Definitions
        // Fuzzy search disabled.
        private List<Result> DefinitionQuery(Query query)
        {
            string queryWord = query.Search.Substring(0, query.Search.Length - 2); // Get the word

            List<Result> results = new List<Result>();

            var word = ecdict.Query(queryWord);

            foreach (var definition in word.definition.Split('\n'))
            {
                results.Add(MakeResultItem(definition, "定义"));
            }

            return results;
        }

        // Exchanges of a word.
        // English -> Exchanges
        // Fuzzy search disabled.
        private List<Result> ExchangeQuery(Query query)
        {
            string queryWord = query.Search.Substring(0, query.Search.Length - 2); // Get the word

            List<Result> results = new List<Result>();

            var word = ecdict.Query(queryWord);

            foreach (var exchange in word.exchange.Split('/'))
            {
                results.Add(MakeResultItem(exchange, "词态变化"));
            }

            return results;
        }


        private bool IsChinese(string cn)
        {
            foreach (char c in cn) { 
                UnicodeCategory cat = char.GetUnicodeCategory(c);
                if (cat == UnicodeCategory.OtherLetter)
                    return true;
            }
            return false;
        }

        public List<Result> Query(Query query)
        {
            ActionWord = query.ActionKeyword;
            string queryWord = query.Search;
            if (queryWord == "") return new List<Result>();
            QueryWord = queryWord;
            if (queryWord.Last() == '!') // An '!' at the end enables detailed query
                return DetailedQuery(query);
            else if (queryWord.Length >= 2 && queryWord.Substring(queryWord.Length - 2, 2) == "!d")
                return DefinitionQuery(query);
            else if (queryWord.Length >= 2 && queryWord.Substring(queryWord.Length - 2, 2) == "!t")
                return TranslationQuery(query);
            else if (queryWord.Length >= 2 && queryWord.Substring(queryWord.Length - 2, 2) == "!e")
                return ExchangeQuery(query);        
            else return FirstLevelQuery(query); // First-level query
        }

        public PluginMetadata Metadata()
        {
           return metadata;
        }
    }
}
